package com.hao.tool.common.util;

import lombok.Getter;
import lombok.Setter;
import lombok.experimental.Accessors;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.Consts;
import org.apache.http.Header;
import org.apache.http.HttpEntity;
import org.apache.http.NameValuePair;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.*;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.StringEntity;
import org.apache.http.entity.mime.MultipartEntityBuilder;
import org.apache.http.entity.mime.content.ContentBody;
import org.apache.http.entity.mime.content.FileBody;
import org.apache.http.entity.mime.content.InputStreamBody;
import org.apache.http.entity.mime.content.StringBody;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.util.EntityUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.multipart.MultipartFile;

import java.io.*;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * HttpClient 远程请求类
 *
 * @author RuiHaoZ on GuangZhou
 * @date 2018/10/11 20:53
 */
public class HttpClientUtil {


    public static HttpClientUtil build() {
        return new HttpClientUtil();
    }

    private static Logger logger = LoggerFactory.getLogger(HttpClientUtil.class);
    private static String CHARSET_STR = "utf-8";
    private static String CHARSET_UTF8 = "utf-8";
    private static ContentType UTF8 = ContentType.create("text/plain", Consts.UTF_8);

    /**
     * 验权码名称
     */
    private String accessTokenKey = "token";

    /**
     * 自定义header
     */
    private Map<String, String> defaultHeaders = new HashMap<>();
    /**
     * 连接超时时间
     */
    private int connectTimeOut = 15000;

    private int sockConnectTimeOut = connectTimeOut * 2;
    /**
     * 打印日志状态
     */
    private boolean logStatus = true;

    public HttpClientUtil addHeader(String key, String value) {
        defaultHeaders.put(key, value);
        return this;
    }

    public HttpClientUtil setChartStr(String chartStr) {
        if (StringUtils.isNotBlank(chartStr)) {
            HttpClientUtil.CHARSET_STR = chartStr;
        }
        return this;
    }


    public HttpClientUtil setHeaders(Map<String, String> headers) {
        defaultHeaders.putAll(headers);
        return this;
    }

    public HttpClientUtil setToken(String accessToken) {
        defaultHeaders.put(accessTokenKey, accessToken);
        return this;
    }

    public HttpClientUtil setTokenKey(String tokenKey) {
        this.accessTokenKey = tokenKey;
        return this;
    }

    public HttpClientUtil setTokenKey(String tokenKey, String accessToken) {
        defaultHeaders.put(tokenKey, accessToken);
        return this;
    }

    public HttpClientUtil setLogStatus(boolean status) {
        this.logStatus = status;
        return this;
    }

    public HttpClientUtil setTimeOut(int millisecond) {
        this.connectTimeOut = millisecond;
        this.sockConnectTimeOut = millisecond * 2;
        return this;
    }


    /**
     * 发送POST请求
     *
     * @param url       请求地址
     * @param stringMap 请求参数
     * @param filesMap  请求文件
     * @return
     */
    public String sendPost(String url, Map<String, String> stringMap,
                           Map<String, File> filesMap) throws IOException {
        return sendPost(url, stringMap, filesMap, new HashMap<>());
    }

    /**
     * http发送请求
     *
     * @param url       请求地址
     * @param stringMap 字符串请求内容
     * @param filesMap  文件请求内容
     * @param inputMap  字节流请求内容
     * @return
     * @throws IOException
     */
    public String sendPost(String url, Map<String, String> stringMap,
                           Map<String, File> filesMap, Map<String, InputFile> inputMap) throws IOException {
        Map<String, ContentBody> bodyList = new HashMap<>();
        if (stringMap != null) {
            stringMap.forEach((k, v) -> bodyList.put(k, new StringBody(v, UTF8)));
        }
        if (filesMap != null) {
            filesMap.forEach((k, v) -> bodyList.put(k, new FileBody(v, UTF8)));
        }
        if (inputMap != null) {
            inputMap.forEach((k, v) -> bodyList.put(k, new InputStreamBody(v.getInputStream(), UTF8, v.getFileName())));
        }
        return sendPostObject(url, bodyList);
    }


    /**
     * post http 发送请求
     *
     * @param url      请求地址
     * @param bodyList 发送内容
     * @return
     * @throws IOException
     */
    public String sendPostObject(String url, Map<String, ContentBody> bodyList) throws IOException {

        // 把一个普通参数和文件上传给下面这个地址 是一个servlet
        HttpPost httpPost = new HttpPost(url);
        // 把文件转换成流对象FileBody
        MultipartEntityBuilder entityBuilder = MultipartEntityBuilder.create();
        entityBuilder.setCharset(Consts.UTF_8);
        bodyList.forEach(entityBuilder::addPart);
        HttpEntity reqEntity = entityBuilder.build();
        httpPost.setEntity(reqEntity);

        return sendAndGetEntityString(httpPost);
    }


    /**
     * @param url
     * @param stringMap
     * @return
     * @throws IOException
     */
    public String sendPost(String url, Map<String, String> stringMap) throws IOException {
        HttpPost httpPost = new HttpPost(url);
        List<NameValuePair> nvps = new ArrayList<>();
        stringMap.forEach((k, v) -> nvps.add(new BasicNameValuePair(k, v)));
        httpPost.setEntity(new UrlEncodedFormEntity(nvps, CHARSET_UTF8));
        return sendAndGetEntityString(httpPost);
    }

    /**
     * 通过字符串发送信息 body_row
     *
     * @param url  请求路径
     * @param json 字符串（对应 postman中的row）
     * @return
     * @throws IOException
     */
    public String sendPostFromRow(String url, String json)
            throws IOException {
        HttpPost httpPost = new HttpPost(url);
        HttpEntity httpEntity = new StringEntity(json, UTF8);
        httpPost.setEntity(httpEntity);
        return sendAndGetEntityString(httpPost);
    }

    /**
     * 发送PUT请求
     *
     * @param url       请求地址
     * @param stringMap 参数
     * @return
     */
    public String sendPut(String url, Map<String, String> stringMap) throws Exception {
        HttpPut httpPut = new HttpPut(url);
        List<NameValuePair> nvps = new ArrayList<>();
        stringMap.forEach((k, v) -> nvps.add(new BasicNameValuePair(k, v)));
        httpPut.setEntity(new UrlEncodedFormEntity(nvps, CHARSET_UTF8));
        return sendAndGetEntityString(httpPut);
    }

    /**
     * 发送GET请求
     *
     * @param url 请求地址
     * @param map 请求参数
     * @return
     */
    public String sendGet(String url, Map<String, String> map) throws IOException {
        String params = convertMapToUrlString(map);
        HttpGet httpGet = new HttpGet(url + params);
        return sendAndGetEntityString(httpGet);
    }

    /**
     * 发送DELETE请求
     *
     * @param url 请求地址
     * @param map 请求参数
     * @return
     */
    public String sendDelete(String url, Map<String, String> map) throws IOException {
        String params = convertMapToUrlString(map);
        HttpDelete httpDelete = new HttpDelete(url + params);
        return sendAndGetEntityString(httpDelete);
    }


    /**
     * 发送post请求，并从流中获取文件
     *
     * @param url       远程路径
     * @param stringMap 请求参数
     * @param filePath  保存本地路径
     * @return
     * @throws IOException
     */
    public String sendPostDown(String url, Map<String, String> stringMap, String filePath) throws IOException {
        HttpPost httpPost = new HttpPost(url);
        List<NameValuePair> nvps = new ArrayList<>();
        stringMap.forEach((k, v) -> nvps.add(new BasicNameValuePair(k, v)));
        httpPost.setEntity(new UrlEncodedFormEntity(nvps, CHARSET_UTF8));
        return sendAndGetFile(httpPost, filePath);
    }

    /**
     * GET 请求下载文件
     *
     * @param url       远程路径
     * @param stringMap 参数
     * @param filePath  保存本地路径
     * @return
     * @throws IOException
     */
    public String sendGetDown(String url, Map<String, String> stringMap, String filePath) throws IOException {
        String params = convertMapToUrlString(stringMap);
        HttpGet httpGet = new HttpGet(url + params);
        return sendAndGetFile(httpGet, filePath);
    }


    /**
     * 请求服务主进程
     *
     * @param httpBase 请求方式
     * @return
     * @throws IOException
     */
    private String sendAndGetEntityString(HttpRequestBase httpBase)
            throws IOException {

        if (StringUtils.isBlank(httpBase.getURI().getPath())) {
            return null;
        }
        RequestConfig requestConfig = RequestConfig.custom()
                .setConnectTimeout(connectTimeOut)
                .setSocketTimeout(sockConnectTimeOut)
                .build();
        httpBase.setConfig(requestConfig);

        CloseableHttpClient httpClient = null;
        if ("https".equals(httpBase.getURI().getScheme())) {
            httpClient = HttpsSSLClient.createSSLInsecureClient();
        } else {
            httpClient = HttpClientBuilder.create().build();
        }

        defaultHeaders.forEach(httpBase::setHeader);
        if (logStatus) {
            logger.info("请求详情：[{}]", httpBase.toString());
        }
        CloseableHttpResponse httpResponse = httpClient.execute(httpBase);

        HttpEntity resEntity = httpResponse.getEntity();
        String response = "";
        response = resEntity != null ? EntityUtils.toString(resEntity, CHARSET_STR) : "";
        String respCode = String.valueOf(httpResponse.getStatusLine().getStatusCode());
        if (logStatus) {
            logger.info("请求返回[{}]:{}", respCode, response);
        }
        if (!"200".equals(respCode)) {
            logger.error("请求出错！:{} 信息为：{}", respCode, response);
        }
        EntityUtils.consume(resEntity);
        httpResponse.close();
        httpClient.close();
        return response;
    }


    /**
     * 从流中获取内容
     *
     * @param url       请求地址
     * @param stringMap 请求参数
     * @return
     * @throws IOException
     */
    public byte[] sendGetReContent(String url, Map<String, String> stringMap) throws IOException {
        HttpGet httpGet = null;
        byte[] img = null;
        if (stringMap == null || stringMap.isEmpty()) {
            httpGet = new HttpGet(url);
        } else {
            String params = convertMapToUrlString(stringMap);
            httpGet = new HttpGet(url + params);
        }
        InputStream inputStream = sendAndGetEntityContent(httpGet);
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        byte[] buffer = new byte[1024];
        int len = 0;
        while ((len = inputStream.read(buffer)) != -1) {
            bos.write(buffer, 0, len);
        }
        inputStream.close();
        img = bos.toByteArray();
        return img;
    }


    /**
     * 请求下载文件并保存在本地
     *
     * @param httpBase 请求方式
     * @param filePath 如果filePath 有值则代表返回的数据中包含文件 as /Users/haol/Downloads
     * @return 保存地址
     * @throws IOException
     */
    private String sendAndGetFile(HttpRequestBase httpBase, String filePath)
            throws IOException {

        CloseableHttpClient httpClient = HttpClientBuilder.create().build();

        defaultHeaders.forEach(httpBase::setHeader);

        if (logStatus) {
            logger.info("请求详情：[{}]", httpBase.toString());
        }
        CloseableHttpResponse httpResponse = httpClient.execute(httpBase);
        HttpEntity resEntity = httpResponse.getEntity();
        if (resEntity != null && resEntity.getContentLength() != 0) {
            String fileName = "";
            Header[] contentDispositions = httpResponse.getHeaders("Content-Disposition");
            if (null != contentDispositions
                    && contentDispositions.length > 0) {
                String contentDisposition = contentDispositions[0].getValue();
                if (StringUtils.isNotBlank(contentDisposition)) {
                    fileName = contentDisposition.substring(contentDisposition.indexOf("filename=") + "filename=".length());
                    logger.info("下载文件为：【{}】", fileName);
                }
            }
            if (!filePath.contains(".") && StringUtils.isNotBlank(fileName)) {
                filePath = filePath + File.separator + fileName;
            }
            saveFile(resEntity.getContent(), filePath);
            return filePath;
        } else {
            throw new IOException("文件不存在,下载失败!");
        }
    }

    /**
     * 将文件从输入流中保存到本地
     *
     * @param in       输入流
     * @param filePath 保存路径 as C:/file/down.zip
     */
    private void saveFile(InputStream in, String filePath) {
        OutputStream out = null;
        try {
            out = new FileOutputStream(new File(filePath));
            byte[] buffer = new byte[4096];
            int readLength = 0;
            while ((readLength = in.read(buffer)) > 0) {
                byte[] bytes = new byte[readLength];
                System.arraycopy(buffer, 0, bytes, 0, readLength);
                out.write(bytes);
            }
            out.flush();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                if (in != null) {
                    in.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
            try {
                if (out != null) {
                    out.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }


    private InputStream sendAndGetEntityContent(
            HttpRequestBase httpBase) throws IOException {
        CloseableHttpClient httpClient = HttpClientBuilder.create().build();

        defaultHeaders.forEach(httpBase::setHeader);

        logger.info("请求详情：[{}]", httpBase.toString());

        CloseableHttpResponse httpResponse = httpClient.execute(httpBase);
        HttpEntity resEntity = httpResponse.getEntity();
        InputStream inputStream = resEntity.getContent();
        String respCode = String.valueOf(httpResponse.getStatusLine().getStatusCode());
        if (!"200".equals(respCode)) {
            throw new IOException("请求出错！:" + respCode);
        }
        return inputStream;
    }

    public String convertMapToUrlString(Map<String, String> map) {
        if (map == null || map.size() == 0) {
            return "";
        }
        StringBuffer sb = new StringBuffer("?");
        int i = 0;
        for (Map.Entry<String, String> stringStringEntry : map.entrySet()) {
            if (i != 0) {
                sb.append("&");
            }
            sb.append(stringStringEntry.getKey());
            sb.append("=");
            sb.append(stringStringEntry.getValue());
            i++;
        }
        return sb.toString();
    }

    @Getter
    @Setter
    @Accessors(chain = true)
    public static class InputFile {
        private String fileName;
        private InputStream inputStream;

        public InputFile(MultipartFile file) throws IOException {
            this.fileName = file.getOriginalFilename();
            this.inputStream = file.getInputStream();
        }

        public InputFile(String fileName, InputStream inputStream) {
            this.fileName = fileName;
            this.inputStream = inputStream;
        }
    }


}
